package com.hyt.it.ogt.ks.service.impl;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import javax.annotation.Resource;

import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.beust.jcommander.internal.Lists;
import com.hyt.common.apiInfor.ApiResponse;
import com.hyt.it.ogt.ks.config.ConfigManager;
import com.hyt.it.ogt.ks.config.KsBizRunTimeException;
import com.hyt.it.ogt.ks.enums.BizStatusCode;
import com.hyt.it.ogt.ks.enums.CandidateStatusCode;
import com.hyt.it.ogt.ks.feign.KwClient;
import com.hyt.it.ogt.ks.feign.model.CandidateDataInitDTO;
import com.hyt.it.ogt.ks.mapper.CandidateAnswerMapper;
import com.hyt.it.ogt.ks.mapper.CandidatePaperMapper;
import com.hyt.it.ogt.ks.mapper.CandidateProcessMapper;
import com.hyt.it.ogt.ks.mapper.ForceItemMapper;
import com.hyt.it.ogt.ks.mapper.PaperTopicMapper;
import com.hyt.it.ogt.ks.model.entity.CandidateAnswer;
import com.hyt.it.ogt.ks.model.entity.CandidatePaper;
import com.hyt.it.ogt.ks.model.entity.CandidateProcess;
import com.hyt.it.ogt.ks.model.entity.ForceItem;
import com.hyt.it.ogt.ks.model.entity.PaperTopic;
import com.hyt.it.ogt.ks.model.vo.InitCandidateDataVO;
import com.hyt.it.ogt.ks.mq.InitCandidateDataProduct;
import com.hyt.it.ogt.ks.service.IPaperInitService;
import com.hyt.it.ogt.ks.service.InitCandidateDataService;
import com.hyt.it.ogt.ks.util.JobThreadPoolTask;
import com.hyt.it.ogt.ks.util.KeyDefineCommon;

import lombok.extern.slf4j.Slf4j;

/**
 * @author wuyingge
 * @ClassName InitCandidateDataServiceImpl.java
 * @Description 手动触发考试-考生试卷初始化
 * @createTime 2021年09月18日 10:13:00
 */
@Service
@Slf4j
public class InitCandidateDataServiceImpl implements InitCandidateDataService {

    @Resource
    private IPaperInitService iPaperInitService;
    @Resource
    private ConfigManager configManager;
    @Resource
    private CandidateProcessMapper candidateProcessMapper;
    @Resource
    private CandidatePaperMapper candidatePaperMapper;
    @Resource
    private PaperTopicMapper paperTopicMapper;
    @Resource
    private ForceItemMapper forceItemMapper;
    @Resource
    private CandidateAnswerMapper candidateAnswerMapper;
    @Resource
    private KwClient kwClient;
    @Resource
    private RedisTemplate redisTemplate;
    @Resource
    private InitCandidateDataProduct initCandidateDataProduct;

    /**
     * 用与消息考生试卷初始化任务
     * @param jobs ：要完成的考生集合
     */
    @Override
    public void consumer(List<InitCandidateDataVO> jobs) {
        if (CollectionUtils.isEmpty(jobs)) {
            log.info("接收到考生初始化试题消息，任务列表为空。");
            return;
        }
        String jobsId = jobs.get(0).getJobId();
        //用与确定一条mq的消息，被消费的幂等性
        String jobsKey = KeyDefineCommon.getInitCandidateJobIdKey(jobsId);
        if(!redisTemplate.opsForValue().setIfAbsent(jobsKey,1)){
            log.error("当前执行考生试卷初始化工作的mq消息已在执行，不能重复消费");
            return;
        }
        try {
            JobThreadPoolTask.<InitCandidateDataVO>run(jobs, job -> {
                log.info("开始执行考生：【{}】，试题初始化工作。", job);
                iPaperInitService.initPaperForTask(job.getExamId(), job.getCandidateId(), job.getAdmissionNumber(), 0);
                log.info("完成执行考生：【{}】，试题初始化工作。", job);
            }, configManager.getInitCandidateDataThread(), "考生试题初始化");
            redisTemplate.delete(jobsKey);
        } catch (Exception e) {
            redisTemplate.delete(jobsKey);
            log.error("接收到考生初始化试题任务执行失败:【{}】", e.getMessage());
        }
    }

    /**
     * 使用rpc形式调用清理数据，容易网关超时  ---  暂时不用
     * @param datas
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    @Deprecated
    public String clearCandidateData(List<InitCandidateDataVO> datas){
        if(CollectionUtils.isEmpty(datas)){
            log.error("收到清理考生试卷初始化任务，但任务内容为空!");
            return "";
        }
        Integer count = datas.get(0).getTotalCount();
        String examId = datas.get(0).getExamId();
        String jobId = datas.get(0).getJobId();
        String jobLock = KeyDefineCommon.getClearCandidateJobIdKey(jobId);
        if(!redisTemplate.opsForValue().setIfAbsent(jobLock,1,2,TimeUnit.HOURS)){
            log.error("收到清理考生试卷初始化任务，但线程没有申请到分布式锁退!");
            return "";
        }
        String clearCountKey = KeyDefineCommon.getInitCandidateCLearProcessCount(datas.get(0).getExamId());
        if(!redisTemplate.hasKey(clearCountKey)){
            redisTemplate.opsForValue().set(clearCountKey,count,2,TimeUnit.HOURS);
        }
        String clearProcessKey = KeyDefineCommon.getInitCandidateCLearProcess(examId);
        try {
            List<String> candidateIds = datas.stream().map(InitCandidateDataVO::getCandidateId).collect(Collectors.toList());
            String flag = clearCandidateData(examId,candidateIds);
            redisTemplate.opsForValue().increment(clearProcessKey,datas.size());
            redisTemplate.expire(clearProcessKey,2L, TimeUnit.HOURS);
            return flag;
        } catch (Exception e){
            log.error("清理考生试卷信息失败",e);
            redisTemplate.opsForValue().increment(clearProcessKey,datas.size());
            redisTemplate.expire(clearProcessKey,2L, TimeUnit.HOURS);
            throw new KsBizRunTimeException(e);
        } finally {
            redisTemplate.delete(jobLock);
        }
    }

    /**
     * 单个清理考生试卷的  暂时不用
     * @param examId
     * @param candidateId
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    @Deprecated
    public Boolean clearCandidateData(String examId,String candidateId){
        String clearCountKey = KeyDefineCommon.getInitCandidateCLearProcessCount(examId);
        try {
            //2:删除candidate_paper中的信息
            LambdaQueryWrapper<CandidatePaper> queryPaper = Wrappers.lambdaQuery();
            queryPaper.eq(CandidatePaper::getExamId,examId).eq(CandidatePaper::getCandidateId,candidateId);
            List<CandidatePaper> candidatePapers = candidatePaperMapper.selectList(queryPaper);
            List<String> candidatePaperIds = candidatePapers.stream().map(CandidatePaper::getId).collect(Collectors.toList());
            candidatePaperMapper.delete(queryPaper);

            //3:删除 paper_topic
            if(!CollectionUtils.isEmpty(candidatePaperIds)){
                LambdaQueryWrapper<PaperTopic> queryTopic = Wrappers.lambdaQuery();
                queryTopic.in(PaperTopic::getCandidatePaperId,candidatePaperIds);
                paperTopicMapper.delete(queryTopic);
            }

            //4:删除force_item
            LambdaQueryWrapper<ForceItem> queryForce = Wrappers.lambdaQuery();
            queryForce.eq(ForceItem::getExamId,examId).eq(ForceItem::getCandidateId,candidateId);
            forceItemMapper.delete(queryForce);

            //5:删除candidate_answer
            LambdaUpdateWrapper<CandidateAnswer> updateAnswer = Wrappers.<CandidateAnswer>lambdaUpdate();
            updateAnswer.eq(CandidateAnswer::getExamId,examId).eq(CandidateAnswer::getCandidateId,candidateId);
            updateAnswer.set(CandidateAnswer::getDelFlag,Boolean.TRUE);
            candidateAnswerMapper.update(null,updateAnswer);
            return Boolean.TRUE;
        } catch (Exception e){
            log.error("清除考生试卷信息失败，考试id:{},考生id：{}",examId,candidateId,e);
            throw new KsBizRunTimeException(e.getCause());
        }
    }

    /**
     * 单个清理考生试卷的  暂时不用
     * @param examId
     * @return
     */
    private String clearCandidateData(String examId, List<String> candidateIds) {
        //查出还未考试中的考生
        LambdaQueryWrapper<CandidateProcess> queryProcess = Wrappers.lambdaQuery();
        queryProcess.eq(CandidateProcess::getExamId, examId).eq(CandidateProcess::getDelFlag, false)
                .in(CandidateProcess::getCandidateId, candidateIds)
                .lt(CandidateProcess::getExamStatus,CandidateStatusCode.TESTING.getCode());
        List<CandidateProcess> candidateProcess = candidateProcessMapper.selectList(queryProcess);
        if(CollectionUtils.isEmpty(candidateProcess)){
            log.error("清理考生试卷信息：当前传入的考生已进入考试阶段");
            throw new KsBizRunTimeException(BizStatusCode.REDO_EXAM_FAIL.getCode(),"当前传入的考生已进入考试阶段");
        }
        //1:去掉candidate_process表中的试卷相关信息
        List<String> pricessIds = candidateProcess.stream().map(CandidateProcess::getId).collect(Collectors.toList());
        List<String> candidateIdList = candidateProcess.stream().map(CandidateProcess::getCandidateId).collect(Collectors.toList());
        LambdaUpdateWrapper<CandidateProcess> updateProcess = Wrappers.lambdaUpdate();
        updateProcess.in(CandidateProcess::getId,pricessIds);
        updateProcess.set(CandidateProcess::getPaperId,null).set(CandidateProcess::getPaperMode,null)
                .set(CandidateProcess::getPaperTimeLength,null).set(CandidateProcess::getPaperScore,null)
                .set(CandidateProcess::getPaperVersion,null);
        candidateProcessMapper.update(null, updateProcess);

        //2:删除candidate_paper中的信息
        LambdaQueryWrapper<CandidatePaper> queryPaper = Wrappers.lambdaQuery();
        queryPaper.eq(CandidatePaper::getExamId,examId).in(CandidatePaper::getCandidateId,candidateIdList);
        List<CandidatePaper> candidatePapers = candidatePaperMapper.selectList(queryPaper);
        List<String> candidatePaperIds = candidatePapers.stream().map(CandidatePaper::getId).collect(Collectors.toList());
        candidatePaperMapper.delete(queryPaper);

        //3:删除 paper_topic
        if(!CollectionUtils.isEmpty(candidatePaperIds)){
            LambdaQueryWrapper<PaperTopic> queryTopic = Wrappers.lambdaQuery();
            queryTopic.in(PaperTopic::getCandidatePaperId,candidatePaperIds);
            paperTopicMapper.delete(queryTopic);
        }

        //4:删除force_item
        LambdaQueryWrapper<ForceItem> queryForce = Wrappers.lambdaQuery();
        queryForce.eq(ForceItem::getExamId,examId).in(ForceItem::getCandidateId,candidateIdList);
        forceItemMapper.delete(queryForce);

        //5:删除candidate_answer
        LambdaUpdateWrapper<CandidateAnswer> updateAnswer = Wrappers.<CandidateAnswer>lambdaUpdate();
        updateAnswer.eq(CandidateAnswer::getExamId,examId).in(CandidateAnswer::getCandidateId,candidateIdList);
        updateAnswer.set(CandidateAnswer::getDelFlag,Boolean.TRUE);
        candidateAnswerMapper.update(null,updateAnswer);
        
        //6:更新kw exam_candidate里边的试卷信息
        String response = kwClient.clearCandidateData(new CandidateDataInitDTO(examId,candidateIds));
        log.info("清理试卷功能调用kw修该考生paperId信息，返回响应信息为:{}",response);
        ApiResponse<Boolean> ksResponse = ApiResponse.ConvertRet(response, Boolean.class);
        if(!Objects.equals(BizStatusCode.KW_SUCCESS_OK.getCode(),ksResponse.getCode()) || !Objects.equals(Boolean.TRUE,ksResponse.getData())){
            log.error("调用考务系统清除ExamCandidate表中的试卷信息失败，考试id:{},考生id:{}",examId, StringUtils.join(candidateIdList,","));
        }
        return "清除考生试卷信息成功";
    }

    /**
     * 消费考生清理试卷信息mq消费者   正在使用
     * @param examId
     * @return
     */
    @Override
    public Boolean consumerClearData(String examId){
        log.info("收到清理考生试卷初始化任务,任务开始,考试id为:【{}】",examId);
        if(Objects.isNull(examId)){
            log.error("收到清理考生试卷初始化任务，但任务内容为空!");
            return Boolean.FALSE;
        }

        //1:查询符合条件的考生
        //没有删除，状态小于或等于30，未开考
        int count = candidateProcessMapper.clearCount(examId,CandidateStatusCode.TESTING.getCode());
        if(count < 1){
            log.info("收到清理考生试卷初始化任务,没有符合要求的考生，任务退出");
            //给kw设置总数
            kwClient.addClearTotalNum(examId,0);
            return Boolean.TRUE;
        }
        //给kw设置总数
        kwClient.addClearTotalNum(examId,count);
        //2:使用代码分页的方式进行处理
        Integer pageSize = configManager.getClearDataSize();
        Integer pageTotal = 0;//总的次数
        List<InitCandidateDataVO> jobs = new ArrayList<>(pageTotal);
        if (count % pageSize == 0) {
            pageTotal = count / pageSize;
        } else {
            pageTotal = count / pageSize + 1;
        }
        for(int i = 1 ; i <= pageTotal; i++ ){
            log.info("清理考生数据进程，当前第:【{}】页，总共有【{}】",i,pageTotal);
            int startIndex = (i -1) * pageSize;
            List<CandidateProcess> cps = candidateProcessMapper.clearList(examId, CandidateStatusCode.TESTING.getCode(), startIndex, pageSize);
            //Map<CandidateId,Id>
            Map<String,String> cpsMap = cps.stream().collect(Collectors.toMap(CandidateProcess::getCandidateId,CandidateProcess::getId,(k1,k2) -> k1));
            List<String> candidateIds = cps.stream().map(CandidateProcess::getCandidateId).filter(Objects::nonNull).collect(Collectors.toList());
            try {
                //clearCandidateInitData(cpsMap,candidateIds,examId);
                //用与打印日志使用
                final int current = i;
                final int pageNo = pageTotal;
                InitCandidateDataVO job = InitCandidateDataVO.builder().examId(examId).candidateIds(candidateIds).cpsMap(cpsMap)
                        .current(current).pageNo(pageNo).type(3).build();
                jobs.add(job);
                //initCandidateDataProduct.sendMessage(jobs);
            } catch (Exception e){
                log.error("清理考生数据进程,查询考生是发生异常",e);
            }
        }
        new Thread(() -> commitJobs(jobs,examId)).start();
        return Boolean.TRUE;
    }

    /**
     * 如果不采用异步提交给线程池处理。有一部分会被先处理，导致分的总数，总页数以不上
     * @param jobs
     */
    private void commitJobs(List<InitCandidateDataVO> jobs,String examId){
        if(CollectionUtils.isEmpty(jobs)){
            log.error("提交考生清理试卷数据任务，任务为空");
            return;
        }
        for(InitCandidateDataVO job : jobs){
            //用与打印日志
            int current = job.getCurrent();
            int totalPapge = job.getPageNo();
            try {
                JobThreadPoolTask.<InitCandidateDataVO>run(Lists.newArrayList(job),j -> {
                    log.info("开始执行清理考生试卷线程，当前第【{}】页，总共【{}】页",current,totalPapge);
                    clearCandidateInitData(j.getCpsMap(),j.getCandidateIds(),j.getExamId());
                    log.info("完成e执行清理考生试卷线程，当前第【{}】页，总共【{}】页",current,totalPapge);
                },"考生试题清理");
            } catch (Exception e){
                log.info("清理考生数据进程，当前第:【{}】页，总共有【{}】,发生异常情况，异常信息:【{}】", current,totalPapge,e);
                continue;
            }
        }
    }

    /**
     * 考生清理试卷数据 具体消费者  正在使用
     * @param cpsMap
     * @param candidateIds
     * @param examId
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void clearCandidateInitData(Map<String,String> cpsMap,List<String> candidateIds,String examId){
        try {
            //1:查出已考试的考生 --- 确认只需要判断ks_candidate_process exam_status  is_start_exam就可以
            //1.1:判断一下如果集合为空，则当前考生都已开考，不能清楚
            if(CollectionUtils.isEmpty(candidateIds)){
                log.info("当前传入的考生都已开考，不能进行数据清楚，考生id为：【{}】",StringUtils.join(candidateIds,","));
                return;
            }

            //2:删除candidate_paper中的信息
            LambdaQueryWrapper<CandidatePaper> queryPaper = Wrappers.lambdaQuery();
            queryPaper.eq(CandidatePaper::getExamId,examId).in(CandidatePaper::getCandidateId,candidateIds);
            List<CandidatePaper> candidatePapers = candidatePaperMapper.selectList(queryPaper);
            List<String> candidatePaperIds = candidatePapers.stream().map(CandidatePaper::getId).collect(Collectors.toList());
            candidatePaperMapper.delete(queryPaper);
            log.debug("【清除考生试卷信息完成】");

            //3:删除 paper_topic
            if(!CollectionUtils.isEmpty(candidatePaperIds)){
                LambdaQueryWrapper<PaperTopic> queryTopic = Wrappers.lambdaQuery();
                queryTopic.in(PaperTopic::getCandidatePaperId,candidatePaperIds);
                paperTopicMapper.delete(queryTopic);
            }
            log.debug("【清除考生试卷topic信息完成】");

            //4:删除force_item  先查出id  --》再去更新或删除
            LambdaQueryWrapper<ForceItem> queryForce = Wrappers.lambdaQuery();
            queryForce.eq(ForceItem::getExamId,examId).in(ForceItem::getCandidateId,candidateIds);
            queryForce.select(ForceItem::getId);
            //List<ForceItem> forceItems = forceItemMapper.selectList(queryForce);
            forceItemMapper.delete(queryForce);
        /*if(!CollectionUtils.isEmpty(forceItems)){
            List<String> forceItemIds = forceItems.stream().map(ForceItem::getId).collect(Collectors.toList());
            forceItemMapper.deleteBatchIds(forceItemIds);
        }*/
            log.debug("【清除考生试卷force_item信息完成】");

            //5:删除candidate_answer  先查出id  --》再去更新或删除
        /*LambdaQueryWrapper<CandidateAnswer> queryAnswer = Wrappers.lambdaQuery();
        queryAnswer.select(CandidateAnswer::getId);
        queryAnswer.eq(CandidateAnswer::getExamId,examId).in(CandidateAnswer::getCandidateId,candidateIds)
                .eq(CandidateAnswer::getDelFlag,Boolean.FALSE);
        List<CandidateAnswer> candidateAnswers = candidateAnswerMapper.selectList(queryAnswer);
        if(!CollectionUtils.isEmpty(candidateAnswers)){
            List<String> answerIds = candidateAnswers.stream().map(CandidateAnswer::getId).collect(Collectors.toList());
            LambdaUpdateWrapper<CandidateAnswer> updateAnswer = Wrappers.<CandidateAnswer>lambdaUpdate();
            updateAnswer.set(CandidateAnswer::getDelFlag,Boolean.TRUE);
            updateAnswer.in(CandidateAnswer::getId,answerIds);
            candidateAnswerMapper.update(null,updateAnswer);
        }*/
            LambdaUpdateWrapper<CandidateAnswer> updateAnswer = Wrappers.<CandidateAnswer>lambdaUpdate();
            updateAnswer.eq(CandidateAnswer::getExamId,examId).in(CandidateAnswer::getCandidateId,candidateIds);
            updateAnswer.set(CandidateAnswer::getDelFlag,Boolean.TRUE);
            candidateAnswerMapper.update(null,updateAnswer);
            log.debug("【清除考生试卷candidate_answer信息完成】");

            //6:更新kw exam_candidate里边的试卷信息
            String response = kwClient.clearCandidateData(new CandidateDataInitDTO(examId,candidateIds));
            log.info("清理试卷功能调用kw修该考生paperId信息，返回响应信息为:{}",response);
            ApiResponse<Boolean> ksResponse = ApiResponse.ConvertRet(response, Boolean.class);
            if(!Objects.equals(BizStatusCode.KW_SUCCESS_OK.getCode(),ksResponse.getCode()) || !Objects.equals(Boolean.TRUE,ksResponse.getData())){
                log.error("调用考务系统清除ExamCandidate表中的试卷信息失败，考试id:{},考生id:{}",examId, StringUtils.join(candidateIds,","));
                throw new KsBizRunTimeException(BizStatusCode.INIT_PAPER_ERROR.getCode(),"清理考生试卷数据，调用考务设置考试考生表paper_id 为null失败");
            }

            //7:清理考生过程表，最后处理是因为如果上面哪一步异常了，还可以进行下一次清理，因为以考生过程表为查询依据的
            List<String> candidateProcessIds = new ArrayList<>(candidateIds.size());
            for(String candidateId : candidateIds){
                String candidateProcessId = cpsMap.get(candidateId);
                if(Objects.nonNull(candidateProcessId)){
                    candidateProcessIds.add(candidateProcessId);
                }
            }
            LambdaUpdateWrapper<CandidateProcess> updateProcess = Wrappers.lambdaUpdate();
            updateProcess.in(CandidateProcess::getId,candidateProcessIds);
            updateProcess.set(CandidateProcess::getPaperId,null).set(CandidateProcess::getPaperMode,null)
                    .set(CandidateProcess::getPaperTimeLength,null).set(CandidateProcess::getPaperScore,null)
                    .set(CandidateProcess::getPaperVersion,null);
            candidateProcessMapper.update(null, updateProcess);
            log.debug("【清除考生考生过程表信息完成】");
        } catch (Exception e){
            log.error("-------------清理考生试卷有一个线程发生了异常。请注意----------",e);
            throw new KsBizRunTimeException();
        } finally {
            //给kw加值
            kwClient.addClearNum(examId,Objects.isNull(candidateIds) ? 0 : candidateIds.size());
        }
    }

    /**
     * 用与消息清理的 mq消息  -- 方法暂时不用
     * @param job
     */
    @Override
    @Deprecated
    public void consumerClearJob(InitCandidateDataVO job){
        List<InitCandidateDataVO> jobs = Lists.newArrayList(job);
        String clearProcessKey = KeyDefineCommon.getInitCandidateCLearProcess(job.getExamId());
        try {
            JobThreadPoolTask.<InitCandidateDataVO>run(jobs,j -> {
                log.info("开始执行清理考生试卷线程，当前第【{}】页，总共【{}】页",j.getCurrent(),j.getPageNo());
                clearCandidateInitData(j.getCpsMap(),j.getCandidateIds(),j.getExamId());
                log.info("完成e执行清理考生试卷线程，当前第【{}】页，总共【{}】页",j.getCurrent(),j.getPageNo());
            },configManager.getInitCandidateDataThread(),"考生试题清理");
            redisTemplate.opsForValue().increment(clearProcessKey,job.getCandidateIds().size());
        } catch (Exception e){
            log.error("提交考生试卷清理任务给线程池发生异常，第【{}】页",job.getPageNo());
            redisTemplate.opsForValue().increment(clearProcessKey,job.getCandidateIds().size());
        }
    }
}
